package com.squareup.moshi;

import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

public abstract class DefaultCollectionJsonAdapter<C extends Collection<T>, T> extends JsonAdapter<C> {

    private final JsonAdapter<T> elementAdapter;

    private DefaultCollectionJsonAdapter(JsonAdapter<T> elementAdapter) {
        this.elementAdapter = elementAdapter;
    }

    @Nullable
    @Override
    public C fromJson(JsonReader reader) throws IOException {
        C result = newCollection();
        try {
            reader.beginArray();
            while (reader.hasNext()) {
                result.add(elementAdapter.fromJson(reader));
            }
            reader.endArray();
        } catch (JsonDataException e) {
            e.printStackTrace();
            reader.skipValue();
        }
        return result;
    }

    @Override
    public void toJson(JsonWriter writer, @Nullable C value) throws IOException {
        writer.beginArray();
        for (T element : value) {
            elementAdapter.toJson(writer, element);
        }
        writer.endArray();
    }

    @Override
    public String toString() {
        return elementAdapter + ".collection()";
    }

    private static <T> JsonAdapter<Collection<T>> newArrayListAdapter(Type type, Moshi moshi) {
        Type elementType = Types.collectionElementType(type, Collection.class);
        JsonAdapter<T> elementAdapter = moshi.adapter(elementType);
        return new DefaultCollectionJsonAdapter<Collection<T>, T>(elementAdapter) {
            @Override
            Collection<T> newCollection() {
                return new ArrayList<>();
            }
        };
    }

    private static <T> JsonAdapter<Set<T>> newLinkedHashSetAdapter(Type type, Moshi moshi) {
        Type elementType = Types.collectionElementType(type, Collection.class);
        JsonAdapter<T> elementAdapter = moshi.adapter(elementType);
        return new DefaultCollectionJsonAdapter<Set<T>, T>(elementAdapter) {
            @Override
            Set<T> newCollection() {
                return new LinkedHashSet<>();
            }
        };
    }

    abstract C newCollection();

    public static Factory newFactory() {
        return new Factory() {
            @Override
            public @Nullable
            JsonAdapter<?> create(
                    Type requestedType,
                    Set<? extends Annotation> annotations,
                    Moshi moshi
            ) {
                Class<?> rawType = Types.getRawType(requestedType);
                if (!annotations.isEmpty()) return null;
                if (rawType == List.class || rawType == Collection.class) {
                    return newArrayListAdapter(requestedType, moshi).nullSafe();
                } else if (rawType == Set.class) {
                    return newLinkedHashSetAdapter(requestedType, moshi).nullSafe();
                }
                return null;
            }
        };
    }

}
